package se.thoka.twt.client.json;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;

import se.thoka.twt.client.json.JSONFieldParserUtil.Parser;
import se.thoka.twt.client.utils.TWTLogLevel;
import se.thoka.twt.client.utils.TWTLogger;

import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONNull;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONValue;
import com.gwtent.reflection.client.ClassType;
import com.gwtent.reflection.client.Field;
import com.gwtent.reflection.client.TypeOracle;

public class TWTJSONParser {
	
	public static <T>Collection<T> parseArray(Class<T> clazz, JSONValue value) {
		Collection<T> result;
		if (isNull(value)) {
			result = null;
		} else {
			result = new ArrayList<T>();
			if (value.isArray() != null) {
				TWTLogger.log("Parsing arrary");
				JSONArray array = value.isArray();
				for (int i = 0; i < array.size(); i++) {
					JSONValue jsonValue = array.get(i);
					T t = parseObject(clazz, jsonValue);
					result.add(t);
				}
			} else if (value.isObject() != null) {
				TWTLogger.log("Parsing singleton array");
				JSONObject singletonList = value.isObject();
				Set<String> keySet = singletonList.keySet();
				if (keySet.size() != 1) {
					throw new IllegalStateException("Expected 1 key for singleton List<" + clazz + ">, got: " + keySet);
				}
				JSONValue singletonObject = singletonList.get(keySet.iterator().next());
				if (singletonObject.isObject() != null) {
					T parsedObject = parseObject(clazz, singletonObject);
					result.add(parsedObject);
				} else if (singletonObject.isArray() != null) {
					result.addAll(parseArray(clazz, singletonObject));
				}
			}
		}
		
		return result;
	}

	public static <T>T parseObject(Class<T> clazz, JSONValue jsonValue) {
		TWTLogger.log("Parsing object of type " + clazz);
		JSONObject object = jsonValue.isObject();
		if (object == null) {
			throw new IllegalArgumentException("JSON value must be an object.");
		}
		ClassType<T> classType = TypeOracle.Instance.getClassType(clazz);
		T result = classType.findConstructor().newInstance();
		
		for (String key : object.keySet()) {
			JSONValue jsonFieldValue = object.get(key);
			Field field = classType.getField(key);
			if (field == null) {
				throw new IllegalStateException("Expected field '" + key + "' in class " + clazz.getName());
			}
			Object value = parseField(field, jsonFieldValue);
			if (value != null) {
				field.setFieldValue(result, value);
			}
		}
		
		return result;
	}
	
	private static Object parseField(Field field, JSONValue value) {
		Object result = null;
		Parser parser = JSONFieldParserUtil.getInstance().getParser(field);
		if (parser != null) {
			try {
				result = parser.parse(field, value);
			} catch (Exception e) {
				TWTLogger.log(TWTLogLevel.ERROR, "Unable to parse " + value + " for field " + field);
			}
		} else {
			TWTLogger.log(TWTLogLevel.ERROR, "Unknown field type: " + field.getType());
		}
		
		return result;
	}
	
	public static boolean isNull(JSONValue value) {
		return value == null || value.isNull() instanceof JSONNull;
	}
	
}
